/*===========================================================================
  Copyright (C) 2011-2014 by the Okapi Framework contributors
-----------------------------------------------------------------------------
  This library is free software; you can redistribute it and/or modify it 
  under the terms of the GNU Lesser General Public License as published by 
  the Free Software Foundation; either version 2.1 of the License, or (at 
  your option) any later version.

  This library is distributed in the hope that it will be useful, but 
  WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License 
  along with this library; if not, write to the Free Software Foundation, 
  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

  See also the full LGPL text here: http://www.gnu.org/copyleft/lesser.html
===========================================================================*/

package net.sf.okapi.applications.lynx;

import java.io.File;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sf.okapi.lib.xliff2.Util;
import net.sf.okapi.lib.xliff2.core.Part.GetTarget;
import net.sf.okapi.lib.xliff2.core.StartXliffData;
import net.sf.okapi.lib.xliff2.core.Fragment;
import net.sf.okapi.lib.xliff2.core.Part;
import net.sf.okapi.lib.xliff2.core.Segment;
import net.sf.okapi.lib.xliff2.core.StartFileData;
import net.sf.okapi.lib.xliff2.core.StartGroupData;
import net.sf.okapi.lib.xliff2.core.Unit;
import net.sf.okapi.lib.xliff2.reader.Event;
import net.sf.okapi.lib.xliff2.reader.XLIFFReader;
import net.sf.okapi.lib.xliff2.writer.XLIFFWriter;

public class Rewriter {
	
	private boolean verbose;
	private boolean pseudoTranslate;
	private boolean join;
	private boolean joinAsMerger;
	private boolean segment;
	private boolean removeAnnotations;
	private boolean removeExtensions;
	private boolean removeModules;
	private String moduleSuffix;
	private boolean withOriginalData;
	private boolean rewrite;
	private Pattern pattern;

	public Rewriter (boolean verbose,
		boolean rewrite,
		boolean pseudoTranslate,
		boolean join,
		boolean joinAsMerger,
		boolean segment,
		boolean removeAnnotations,
		boolean removeExtensions,
		boolean removeModules,
		String moduleSuffix,
		boolean withOriginalData)
	{
		this.verbose = verbose;
		this.rewrite = rewrite;
		this.join = join;
		this.joinAsMerger = joinAsMerger;
		this.segment = segment;
		this.pseudoTranslate = pseudoTranslate;
		this.removeAnnotations = removeAnnotations;
		this.removeExtensions = removeExtensions;
		this.removeModules = removeModules;
		this.moduleSuffix = moduleSuffix;
		this.withOriginalData = withOriginalData;
	}
	
	public void process (List<File> list) {
		XLIFFWriter writer = null;
		try ( XLIFFReader reader = new XLIFFReader(XLIFFReader.VALIDATION_MAXIMAL) ) {
			File output = null;
			for ( File input : list ) {
				// Show the input path
				if ( rewrite ) System.out.print(" ");
				System.out.println("Input: "+input.getAbsolutePath());

				if ( rewrite ) {
					// Compute the output path
					String path = input.getAbsolutePath();
					String ext = "";
					int n = path.lastIndexOf('.');
					if ( n > -1 ) {
						ext = path.substring(n);
						path = path.substring(0, n);
					}
					output = new File(path+".out"+ext);
					// Display the output path
					System.out.println("Output: "+output.getAbsolutePath());
				}
				
				// Open the input and create the output
				reader.open(input.toURI());
				
				if ( rewrite ) {
					writer = new XLIFFWriter();
					writer.setWithOriginalData(withOriginalData);
					writer.create(output, "will-be-replaced");
				}
				
				while ( reader.hasNext() ) {
					// Get the event
					Event event = reader.next();
					
					// Display the trace
					switch ( event.getType() ) {
					case START_XLIFF:
						StartXliffData dd = event.getStartXliffData();
						printLine("Start document");
						printLine(String.format("source language=%s", dd.getSourceLanguage()));
						printLine(String.format("target language=%s", dd.getTargetLanguage()==null ? "<none declated>" : dd.getTargetLanguage()));
						if ( pseudoTranslate ) {
							if ( dd.getTargetLanguage() == null ) {
								dd.setTargetLanguage("x-pseudo");
							}
						}
						break;
					case START_FILE:
						StartFileData sd = event.getStartFileData();
						printLine(String.format("Start section (file id=%s):", sd.getId()));
						printLine(String.format("original=%s", sd.getOriginal()));
						break;
					case START_GROUP:
						StartGroupData gd = event.getStartGroupData();
						printLine(String.format("Start group (id=%s):", gd.getId()));
						break;
					case TEXT_UNIT:
						Unit unit = event.getUnit();
						if ( join ) {
							if ( joinAsMerger ) {
								unit.join(0, -1, true, true, true);
							}
							else {
								unit.joinAll(false, false);
							}
						}
						if ( segment ) {
							segment(unit);
						}
						if ( pseudoTranslate ) {
							pseudoTranslate(unit);
						}
						if ( removeAnnotations ) {
							unit.removeMarkers();
						}
						printLine(String.format("Unit (id=%s):", unit.getId()));
						for ( Part part : unit ) {
							printPart(part);
						}
						break;
					case END_GROUP:
						printLine("End group");
						break;
					case END_FILE:
						printLine("End section");
						break;
					case END_XLIFF:
						printLine("End document");
						System.out.println("--------------------");
						break;
					case INSIGNIFICANT_PART:
					case SKELETON:
					case START_DOCUMENT:
					case END_DOCUMENT:
					default:
						break;
					}
					
					if ( removeExtensions ) {
						Util.removeExtensions(event.getResource());
					}
					if ( removeModules ) {
						Util.removeModules(event.getResource(), moduleSuffix);
					}
					
					// Re-write the event
					if ( writer != null ) {
						writer.writeEvent(event);
					}
				}
				
				if ( writer != null ) {
					writer.close();
				}
			}
		}
		finally {
			if ( writer != null ) {
				writer.close();
			}
		}
	}

	private void printLine (String text) {
		if ( verbose ) {
			System.out.println(text);
		}
	}
	
	private void printPart (Part part) {
		if ( !verbose ) return;
		if ( part.isSegment() ) {
			Segment seg = (Segment)part;
			System.out.println(String.format(" segment (id=%s):", seg.getId()));
			System.out.print("  source=");
			System.out.println("["+seg.getSource().toXLIFF(null, null, withOriginalData)+"]");
			System.out.print("  target=");
			if ( seg.hasTarget() ) {
				System.out.println("["+seg.getTarget().toXLIFF(null, null, withOriginalData)+"]");
			}
			else {
				System.out.println("<no target defined>");
			}
		}
		else {
			System.out.println(" ignorable:");
			System.out.print("  source=");
			System.out.println("["+part.getSource().toXLIFF(null, null, withOriginalData)+"]");
			System.out.print("  target=");
			if ( part.hasTarget() ) {
				System.out.println("["+part.getTarget().toXLIFF(null, null, withOriginalData)+"]");
			}
			else {
				System.out.println("<no target defined>");
			}
		}
		
	}
	
	private void pseudoTranslate (Unit unit) {
		// Create target based on source
		for ( Part part : unit ) {
			// Create the new target fragment as a copy of the source
			part.setTarget(new Fragment(part.getSource(), part.getStore(), true));
		}
		
		// Pseudo translate the target
		unit.hideProtectedContent();
		for ( Part part : unit ) {
			if ( !part.isSegment() ) continue;
			
			// If there is no target or if the target is empty: create one copied from the source
			Fragment frag;
			if ( !part.hasTarget() || part.getTarget().isEmpty()  ) {
				frag = part.getTarget(GetTarget.CLONE_SOURCE);
			}
			else {
				frag = part.getTarget();
			}
			// Existing target content is overwritten
			
			String ct = frag.getCodedText();
			StringBuilder tmp = new StringBuilder(ct);
			for ( int i=0; i<ct.length(); i++ ) {
				char ch = ct.charAt(i);
				if ( Fragment.isChar1(ch) ) {
					i++; // Skip
				}
				else if ( Character.isDigit(ch) ) {
					tmp.setCharAt(i, 'N');
				}
				else if (( ch == 'A' ) || ( ch == 'a' )) {
					tmp.setCharAt(i, '\u0001');
				}
				else if ( ch == '\u0001' ) {
					tmp.setCharAt(i, 'A');
				}
				else if ( Character.isLetter(ch) ) {
					tmp.setCharAt(i, 'Z');
				}
			}
			// Done: set back the modified content
			frag.setCodedText(tmp.toString());
		}
		unit.showProtectedContent();
	}

	private void segment (Unit unit) {
		 if ( pattern == null ) {
			 pattern = Pattern.compile("\\.[ \\t\\n]");
		 }
		 
		 for ( int partIndex=0; partIndex<unit.getPartCount(); partIndex++ ) {
			 if ( !unit.getPart(partIndex).isSegment() ) continue;
			 Segment seg = (Segment)unit.getPart(partIndex);
			 // Skip segment that we cannot re-segment
			 if ( !seg.getCanResegment() ) continue;

			 // Find break opportunities
			 String srcCt = seg.getSource().getCodedText();
			 Matcher srcMatcher = pattern.matcher(srcCt);
			 
			 Matcher trgMatcher = null;
			 if ( seg.hasTarget() ) {
				 String trgCt = seg.getTarget().getCodedText();
				 trgMatcher = pattern.matcher(trgCt);
			 }
			 int srcEnd = 0;
			 int trgEnd = 0;
			 if ( srcMatcher.find() ) {
				 srcEnd = srcMatcher.end();
			 }
			 if (( trgMatcher != null ) && trgMatcher.find() ) {
				 trgEnd = trgMatcher.end();
			 }
			 if (( srcEnd != 0 ) || ( trgEnd != 0 )) {
				 unit.split(partIndex, srcEnd, srcEnd, trgEnd, trgEnd, true);
			 }
			 // Go to next segment, it will be the new segment if one was created
		 }
	}

}
